package com.example.mydemo1.config;

import com.example.mydemo1.config.AppException;
import com.example.mydemo1.config.AppErrorCode;
import com.example.mydemo1.entity.common.AppResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.xml.bind.ValidationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <pre>
 * 全局异常处理
 * 这里的异常返回消息算是比较多的，正常安全考虑的话不会返回异常的具体消息，
 * 一般返回异常码和一些不涉及逻辑判断的描述，有必要请修改下面错误描述，
 * 而异常码和异常码描述可以放到常量类中，也可以放入到数据库中，看自己方便。
 * </pre>
 */
@ControllerAdvice
@ResponseBody
public class CommonExceptionAdvice {

    private static Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public AppResp<Void> handleException(Exception e) {
        String message = String.format("500，服务器端发生错误，错误信息为：%s", e.getMessage());
        logger.error(message, e);
        return AppResp.error(AppErrorCode.Exception.getCode(), message);
    }

    /**
     * 200 - OK，请求正常，但业务代码发生异常，抛出了AppException异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(AppException.class)
    public AppResp<Void> handleServiceException(AppException e) {
        String message = String.format("200，请求正常但业务处理异常，详情：%s", e.getMessage());
        logger.error(message, e);
        return AppResp.error(AppErrorCode.AppException.getCode(), message);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public AppResp<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        //required_parameter_is_not_present
        logger.error("400，缺少请求参数，详情：{}", e.getMessage(), e);
        return AppResp.error(AppErrorCode.MissingServletRequestParameterException.getCode(), "400，缺少请求参数");
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public AppResp<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        //could_not_read_json
        logger.error("400，参数解析失败，详情：{}", e.getMessage(), e);
        return AppResp.error(AppErrorCode.HttpMessageNotReadableException.getCode(), "400，参数解析失败，如缺少参数、参数名称错误、参数值格式错误等");
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AppResp<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        List<FieldError> errors = result.getFieldErrors();

        Map<String, String> resultMsg = new HashMap<>();
        for (FieldError fieldError : errors) {
            resultMsg.put(fieldError.getField(), fieldError.getDefaultMessage());
        }

        String message = String.format("400，参数校验失败，错误信息为：%s", resultMsg);

        logger.error(message, e);
        return AppResp.error(AppErrorCode.MethodArgumentNotValidException.getCode(), message);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public AppResp<Void> handleBindException(BindException e) {
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String field = error.getField();
        String exception = error.getDefaultMessage();
        String all = String.format("%s:%s", field, exception);
        String message = String.format("400，%s绑定失败，如参数格式错误等， %s", field, all);

        logger.error(message, e);
        return AppResp.error(AppErrorCode.BindException.getCode(), message);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public AppResp<Void> handleValidationException(ValidationException e) {
        //validation_exception
        logger.error("参数验证失败，详情：", e);
        return AppResp.error(AppErrorCode.ValidationException.getCode(), "参数验证失败");
    }

    /**
     * 404 - Not Found
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public AppResp<Void> defaultErrorHandler(Exception e) {
        logger.error("404，请求的资源不存在，详情：", e);
        return AppResp.error(AppErrorCode.NoHandlerFoundException.getCode(), "404，请求的资源不存在");
    }

    /**
     * 405 - Method Not Allowed
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AppResp<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        //request_method_not_supported
        logger.error("405，方法不被允许，详情：", e);
        return AppResp.error(AppErrorCode.HttpRequestMethodNotSupportedException.getCode(), "405，方法不被允许。请查看请求方法类型是否正确，如POST请求却以GET方式发送。");
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public AppResp<Void> handleHttpMediaTypeNotSupportedException(Exception e) {
        //content_type_not_supported
        logger.error("415，不支持的内容类型，详情：", e);
        return AppResp.error(AppErrorCode.HttpMediaTypeNotSupportedException.getCode(), "415，不支持的内容类型。请查看参数传递类型是否正确，如Content-Type为application/json的却以multipart/form-data方式发送参数。");
    }

    /**
     * 操作数据库出现异常:名称重复，外键关联
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public AppResp<Void> handleException(DataIntegrityViolationException e) {
        //操作数据库出现异常：字段重复、有外键关联等
        String message = String.format("500，操作数据库出现异常，如唯一约束，外键关联等异常，错误信息为：%s", e.getMessage());
        logger.error(message, e);
        return AppResp.error(AppErrorCode.DataIntegrityViolationException.getCode(), message);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public AppResp<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        String message = String.format("500，服务器内部错误，方法参数类型不匹配，错误信息为：%s", e.getMessage());
        logger.error(message, e);
        return AppResp.error(AppErrorCode.MethodArgumentTypeMismatchException.getCode(), message);
    }

}
